package com.sfdk.graphics.engine.data
{
    import com.sfdk.data.api.ITreeDataProvider;
    import com.sfdk.events.IOEvent;
    import com.sfdk.graphics.engine.api.IGraphicElement;
    import com.sfdk.graphics.engine.api.ILayerInformation;
    import com.sfdk.graphics.engine.api.IMapObject;
    import com.sfdk.graphics.engine.api.ITileset;
    import com.sfdk.graphics.engine.api.IWalkableLayerInformation;
    import com.sfdk.graphics.engine.core.enums.LayerType;
    import com.sfdk.graphics.engine.core.enums.RendererType;
    import com.sfdk.graphics.engine.data.layers.Layer;
    import com.sfdk.graphics.engine.data.layers.WalkableLayer;
    import com.sfdk.io.GenericIO;
    import com.sfdk.utils.Base64;
    import com.sfdk.utils.IntPoint;
    import com.sfdk.utils.pathfinder.IAStarSearchable;

    import flash.display.BitmapData;
    import flash.events.Event;
    import flash.utils.ByteArray;

    public class Map extends GenericIO implements IMapObject, IAStarSearchable, ITreeDataProvider
    {
        public function Map(currentDirectory:String = null, filePath:String = null, layerClass:Class = null, walkableLayerType:Class = null) {
            _fullName = filePath;
            if (layerClass != null)
                _layerClass = layerClass;
            else
                _layerClass = Layer;
            if (filePath != null) {
                var pathArray:Array = filePath.split("/");
                _type = pathArray[pathArray.length - 2];
                _name = pathArray[pathArray.length - 1];
            }
            super(currentDirectory + filePath, GenericIO.XML_FILE);
        }

        //-------------------------------------------------------
        //
        //  properties
        //
        //-------------------------------------------------------

        private const TILE_SIZE:int = 24;
        private const MAX_SIZE:int = 280;

        private var _layerClass:Class;
        private var _fullName:String;
        private var _name:String;
        private var _type:String;
        private var _maxTilesetID:int = 0;

        public function get fullName():String {
            return _fullName;
        }

        public function get label():String {
            return _name;
        }

        public function set name(value:String):void {
            _name = value;
        }

        public function get type():String {
            return _type;
        }

        public function get rootType():String {
            return "";
        }

        public function get isImage():Boolean {
            return false;
        }

        public function get imageData():BitmapData {
            return null;
        }

//----------------------------------
        // type
        //----------------------------------

        /**
         * @private
         */
        private var _rendererType:RendererType = RendererType.TILE_BASED;

        public function get rendererType():RendererType {
            return _rendererType;
        }

        //----------------------------------
        // lowerLimit
        //----------------------------------

        /**
         * @private
         */
        private var _lowerLimit:int = MAX_SIZE;

        public function get lowerLimit():int {
            return _lowerLimit;
        }


        public function set lowerLimit(value:int):void {
            _lowerLimit = value;
        }

        //----------------------------------
        // upperLimit
        //----------------------------------

        /**
         * @private
         */
        private var _upperLimit:int;

        public function get upperLimit():int {
            return _upperLimit;
        }

        public function set upperLimit(value:int):void {
            _upperLimit = value;
        }

        //----------------------------------
        // leftLimit
        //----------------------------------

        /**
         * @private
         */
        private var _leftLimit:int;

        public function get leftLimit():int {
            return _leftLimit;
        }


        public function set leftLimit(value:int):void {
            _leftLimit = value;
        }

        //----------------------------------
        // rightLimit
        //----------------------------------

        /**
         * @private
         */
        private var _rightLimit:int = MAX_SIZE;

        public function get rightLimit():int {
            return _rightLimit;
        }


        public function set rightLimit(value:int):void {
            _rightLimit = value;
        }

//----------------------------------
        // totalWidthInTile
        //----------------------------------

        /**
         * @private
         */
        private var _totalWidthInTile:int = MAX_SIZE;

        public function get totalWidthInTile():int {
            return _totalWidthInTile;
        }

        //----------------------------------
        // totalHeightInTile
        //----------------------------------

        /**
         * @private
         */
        private var _totalHeightInTile:int = MAX_SIZE;

        public function get totalHeightInTile():int {
            return _totalHeightInTile;
        }

        //----------------------------------
        // widthInTile
        //----------------------------------

        /**
         * @private
         */
        protected var _widthInTile:int = MAX_SIZE;

        public function get widthInTile():int {
            return _widthInTile;
        }

        //----------------------------------
        // heightInTile
        //----------------------------------

        /**
         * @private
         */
        protected var _heightInTile:int = MAX_SIZE;

        public function get heightInTile():int {
            return _heightInTile;
        }

        //----------------------------------
        // width
        //----------------------------------

        /**
         * @private
         */
        protected var _width:Number = MAX_SIZE * TILE_SIZE;

        public function get width():Number {
            return _width;
        }

        public function set width(value:Number):void {
            _width = value;
        }

        public function getWidth():int {
            return _widthInTile;
        }

        //----------------------------------
        // height
        //----------------------------------

        /**
         * @private
         */
        protected var _height:Number = MAX_SIZE * TILE_SIZE;

        public function get height():Number {
            return _height;
        }

        public function set height(value:Number):void {
            _height = value;
        }

        public function getHeight():int {
            return _heightInTile;
        }

        //----------------------------------
        // tileSize
        //----------------------------------

        public function get tileSize():int {
            return TILE_SIZE;
        }

        //----------------------------------
        // fileName
        //----------------------------------

        /**
         * @private
         */
        private var _fileName:String;

        public function get fileName():String {
            return _fileName;
        }

        //----------------------------------
        // version
        //----------------------------------

        /**
         * @private
         */
        private var _version:String;

        public function get version():String {
            return _version;
        }

        //----------------------------------
        // interieurList
        //----------------------------------

        /**
         * @private
         */
        private var _interieurList:Vector.<Map>;

        public function get interieurList():Vector.<Map> {
            return _interieurList;
        }

        //----------------------------------
        // tilesetList
        //----------------------------------

        /**
         * @private
         */
        private var _tilesetList:Vector.<ITileset> = new Vector.<ITileset>();

        public function get tilesetList():Vector.<ITileset> {
            return _tilesetList;
        }

        //----------------------------------
        // solLayers
        //----------------------------------

        /**
         * @private
         */
        private var _solLayers:Vector.<ILayerInformation> = new Vector.<ILayerInformation>();

        public function get solLayers():Vector.<ILayerInformation> {
            return _solLayers;
        }

        //----------------------------------
        // upperLayers
        //----------------------------------

        /**
         * @private
         */
        private var _upperLayers:Vector.<ILayerInformation> = new Vector.<ILayerInformation>();

        public function get upperLayers():Vector.<ILayerInformation> {
            return _upperLayers;
        }

        //----------------------------------
        // joueurLayers
        //----------------------------------

        /**
         * @private
         */
        private var _joueurLayers:Vector.<ILayerInformation> = new Vector.<ILayerInformation>();

        public function get joueurLayers():Vector.<ILayerInformation> {
            return _joueurLayers;
        }

        //----------------------------------
        // walkableLayer
        //----------------------------------

        /**
         * @private
         */
        private var _walkableLayer:IWalkableLayerInformation;

        public function get walkableLayer():IWalkableLayerInformation {
            return _walkableLayer;
        }

        //----------------------------------
        // isWalkable
        //----------------------------------

        public function isWalkable(x:int, y:int):Boolean {
            return (WalkableLayer(_walkableLayer).isWalkable(x, y));
        }

        public function isWalkableUnder(graphicElement:IGraphicElement):Boolean {
            return isWalkable(graphicElement.x / (TILE_SIZE), graphicElement.y / (TILE_SIZE));
        }


        public function clearTempWalkable():void
        {
            WalkableLayer(_walkableLayer).clearTempWalkable();
        }

        public function addTempUnwalkable(turnStartPos:IntPoint):void {
            WalkableLayer(_walkableLayer).addTempUnwalkable(turnStartPos);
        }

        //----------------------------------
        // musicName
        //----------------------------------

        private var _musicName:String;

        public function get musicName():String {
            return _musicName;
        }

        //----------------------------------
        // npcList
        //----------------------------------

        private var _npcList:Vector.<NPCDefinition>;


        public function get npcList():Vector.<NPCDefinition> {
            return _npcList;
        }

        private var _mapType:String;
        private var _enemyList:Vector.<EnemyDefinition>;
        private var _forceList:Vector.<ForceDefinition>;


        public function get enemyList():Vector.<EnemyDefinition> {
            return _enemyList;
        }

        public function get forceList():Vector.<ForceDefinition> {
            return _forceList;
        }

        public function get mapType():String {
            return _mapType;
        }

        //--------------------------------------------------------------------------
        //
        //  overriden methods
        //
        //--------------------------------------------------------------------------

        protected override function init():void {
            _version = _fileInfo.@version;
            _name = _fileInfo.@name;
            _rendererType = new RendererType(int(Number(_fileInfo.@renderType)));
            _mapType = _fileInfo.@type;

            _totalWidthInTile = Number(_fileInfo.@width);
            _totalHeightInTile = Number(_fileInfo.@width);

            _lowerLimit = Number(_fileInfo.limits.@lower);
            _upperLimit = Number(_fileInfo.limits.@upper);
            _leftLimit = Number(_fileInfo.limits.@left);
            _rightLimit = Number(_fileInfo.limits.@right);

            _musicName = _fileInfo.music[0];
            var tmp:int;
            if (_leftLimit > _rightLimit) {
                tmp = _leftLimit;

                _leftLimit = _rightLimit;
                _rightLimit = tmp;
            }
            if (_upperLimit > _lowerLimit) {
                tmp = _upperLimit;

                _upperLimit = _lowerLimit;
                _lowerLimit = tmp;
            }
            _widthInTile = (_rightLimit - _leftLimit);
            _heightInTile = (_lowerLimit - _upperLimit);
            _width = (_rightLimit - _leftLimit) * TILE_SIZE;
            _height = (_lowerLimit - _upperLimit) * TILE_SIZE;

            addEventListener("insideMapParsed", onComponentLoaded);
            addEventListener("allTilesetsLoaded", onComponentLoaded);
            addEventListener("layerParsed", onComponentLoaded);
            addEventListener("allNpcLoaded", onComponentLoaded);
            addEventListener("battleDetailsLoaded", onComponentLoaded);

            parseInsideMap();

            parseTilesets();

            parseLayers();

            parseNPCList();

            parseBattleDetails();
        }

        //--------------------------------------------------------------------------
        //
        //  methods
        //
        //--------------------------------------------------------------------------

        //----------------------------------
        // getTileset
        //----------------------------------

        public function getTileset(tilesetId:int):ITileset {
            var tileset:ITileset = null;
            for each(tileset in _tilesetList) {
                if (tileset.id == tilesetId)
                    break;
            }
            return tileset;
        }

        private function parseInsideMap():void {
            var interieurList:XMLList = _fileInfo.descendants("interieur");
            var currentInterieur:Map;
            var i:int, interieurCount:int = interieurList.length();

            _interieurList = new Vector.<Map>();

            for (i = 0; i < interieurCount; i++) {
                currentInterieur = new Map(interieurList[i].@file);
                _interieurList.push(currentInterieur);
            }

            dispatchEvent(new Event("insideMapParsed"));
        }

        private function parseTilesets():void {
            var tilesetList:XMLList = _fileInfo.descendants("tileset");
            var i:int, tilesetCount:int = tilesetList.length();
            var currentTileset:Tileset;

            _tilesetList = new Vector.<ITileset>();
            _tilesetToLoad = tilesetCount + 1;

            for (i = 0; i < tilesetCount; i++) {
                currentTileset = new Tileset("http://dev.lushu.org/sfdk/tilesets/output/", tilesetList[i].@source, int(Number(tilesetList[i].@id)), TILE_SIZE);
                currentTileset.addEventListener("tilesetLoaded", onTilesetLoaded);
                _tilesetList.push(currentTileset);
                _maxTilesetID = Math.max(_maxTilesetID, currentTileset.id);
            }
            currentTileset = new Tileset("http://dev.lushu.org/sfdk/tilesets/output/", "walkable/walkableTileset.png", 254, TILE_SIZE);
            currentTileset.addEventListener("tilesetLoaded", onTilesetLoaded);
            _tilesetList.push(currentTileset);

            if (tilesetCount == 0)
                dispatchEvent(new Event("allTilesetsLoaded"));
        }

        private function parseNPCList():void {
            var npcList:XMLList = _fileInfo.descendants("npc");
            var i:int, npcCount:int = npcList.length();
            var currentNpc:NPCDefinition;

            _npcList = new Vector.<NPCDefinition>();

            for (i = 0; i < npcCount; i++) {
                currentNpc = new NPCDefinition(npcList[i]);
                _npcList.push(currentNpc);
            }

            dispatchEvent(new Event("allNpcLoaded"));
        }

        private function parseBattleDetails():void {
            var forcePosition : XMLList = _fileInfo.descendants("force");
            var i:int, forcePositionCount:int = forcePosition.length();
            _forceList = new Vector.<ForceDefinition>();

            for (i = 0; i < forcePositionCount; i++) {
                _forceList.push(new ForceDefinition(forcePosition[i]));
            }

            var enemyDetails : XMLList = _fileInfo.descendants("enemy");
            var enemyCount:int = enemyDetails.length();
            _enemyList = new Vector.<EnemyDefinition>();

            for (i = 0; i < enemyCount; i++) {
                _enemyList.push(new EnemyDefinition(enemyDetails[i]));
            }
        }

        private function parseLayers():void {
            var layer:ILayerInformation;

            _solLayers = new Vector.<ILayerInformation>;
            var solLayersXML:XMLList = _fileInfo.descendants("solLayers")[0].descendants("layer");
            var layerXML:XML;
            for each(layerXML in solLayersXML) {
                layer = new _layerClass(layerXML, this, LayerType.SOL_TYPE);
                _solLayers.push(layer);
            }

            _joueurLayers = new Vector.<ILayerInformation>();
            var joueurLayersXML:XMLList = _fileInfo.descendants("joueurLayers")[0].descendants("layer");
            for each(layerXML in joueurLayersXML) {
                layer = new _layerClass(layerXML, this, LayerType.JOUEUR_TYPE);
                _joueurLayers.push(layer);
            }


            _upperLayers = new Vector.<ILayerInformation>();
            var upperLayersXML:XMLList = _fileInfo.descendants("upperLayers")[0].descendants("layer");
            for each(layerXML in upperLayersXML) {
                layer = new _layerClass(layerXML, this, LayerType.UPPER_TYPE);
                _upperLayers.push(layer);
            }

            var walkableLayersXML:XMLList = _fileInfo.descendants("walkableLayers")[0].descendants("layer");
            _walkableLayer = new WalkableLayer(walkableLayersXML[0], this, LayerType.WALKABLE_TYPE);

            dispatchEvent(new Event("layerParsed"));
        }


        public function cleanTilesets():void {
            // TODO : No cleaning for editable map, find a more clean way to differantiate the two kinds
            /*for (var i:int = 0; i < _tilesetList.length; i++) {
             _tilesetList[i].emptyTileset();
             }*/
        }

        //--------------------------------------------------------------------------
        //
        //  event handlers
        //
        //--------------------------------------------------------------------------

        private var _tilesetToLoad:int;

        private function onTilesetLoaded(e:Event):void {
            _tilesetToLoad--;
            if (_tilesetToLoad == 0) {
                dispatchEvent(new Event("allTilesetsLoaded"));
            }
        }

        protected var _componentToLoad:int = 4;

        protected function onComponentLoaded(e:Event):void {
            _componentToLoad--;
            if (_componentToLoad == 0) {
                dispatchEvent(new Event("mapCreated"));
                dispatchEvent(new IOEvent(IOEvent.LOAD_COMPLETE));
            }
        }

        public function write():XML {

            var out:XML = new XML('<?xml version="1.0"?><map version="2.1" name="' + _name + '" width="' + _totalWidthInTile + '" height="' + _totalHeightInTile + '" renderType="' + _rendererType.kind + '"><limits left="' + _leftLimit + '" right="' + _rightLimit + '" upper="' + _upperLimit + '" lower="' + _lowerLimit + '"/><tilesetList/></map>');

            var tileset:ITileset;
            for each(tileset in _tilesetList) {
                if (tileset.id != 254) {
                    var tilesetXML:XML = new XML('<tileset source="' + tileset.fileName + '" id="' + tileset.id + '"/>');
                    out.tilesetList.appendChild(tilesetXML);
                }
            }

            var layer:ILayerInformation;

            var solLayersXML:XML = new XML('<solLayers></solLayers>');
            for each(layer in _solLayers) {
                solLayersXML.appendChild(writeLayer(layer));
            }
            out.appendChild(solLayersXML);

            var joueurLayersXML:XML = new XML('<joueurLayers></joueurLayers>');
            for each(layer in _joueurLayers) {
                joueurLayersXML.appendChild(writeLayer(layer));
            }
            out.appendChild(joueurLayersXML);

            var upperLayersXML:XML = new XML('<upperLayers></upperLayers>');
            for each(layer in _upperLayers) {
                upperLayersXML.appendChild(writeLayer(layer));
            }
            out.appendChild(upperLayersXML);

            var walkableLayerXML:XML = new XML('<walkableLayers></walkableLayers>');
            walkableLayerXML.appendChild(writeLayer(_walkableLayer));
            out.appendChild(walkableLayerXML);

            return out;
        }

        private function writeLayer(layer:ILayerInformation):XML {
            var layerXML:XML = null;
            if (layer != null) {
                var y:int;
                var x:int;
                var k:int;
                layerXML = new XML('<layer name="' + layer.name + '"><data></data></layer>');

                var input:Vector.<Vector.<TileInfo>> = layer.tileData;
                var byteArray:ByteArray = new ByteArray();
                for (y = 0; y < input.length; y++) {
                    for (x = 0; x < input[y].length; x++) {
                        var tileInfo:TileInfo = input[y][x];

                        k = (x + y * input[y].length) * 6;

                        var tilesetId:int = 0;
                        var tileX:int = -1;
                        var tileY:int = -1;
                        if (tileInfo != null) {
                            if (tileInfo.tileset != null)
                                tilesetId = tileInfo.tileset.id;
                            tileX = tileInfo.tileX;
                            tileY = tileInfo.tileY;
                        }

                        //noinspection MagicNumberJS
                        var LAST_BYTE:int = 0x000000FF;

                        byteArray[k] = (tilesetId & LAST_BYTE);
                        byteArray[k + 1] = ((tilesetId >> 8) & LAST_BYTE);

                        byteArray[k + 2] = (tileX & LAST_BYTE);
                        byteArray[k + 3] = ((tileX >> 8) & LAST_BYTE);

                        byteArray[k + 4] = (tileY & LAST_BYTE);
                        byteArray[k + 5] = ((tileY >> 8) & LAST_BYTE);

                    }
                }
                byteArray.compress();

                layerXML.data = Base64.encode(byteArray);
            }
            return layerXML;
        }

        //--------------------------------------------------------------------------
        //
        //  editor methods
        //
        //--------------------------------------------------------------------------

        public function addLayer(layerType:LayerType, name:String):void {
            var layer:Layer = new _layerClass(null, this, layerType);
            layer.name = name;
            switch (layerType) {
                case LayerType.SOL_TYPE:
                    _solLayers.push(layer);
                    break;

                case LayerType.JOUEUR_TYPE:
                    _joueurLayers.push(layer);
                    break;

                case LayerType.UPPER_TYPE:
                    _upperLayers.push(layer);
                    break;

                case LayerType.WALKABLE_TYPE:
                    _walkableLayer = new WalkableLayer(null, this, layerType);
                    (_walkableLayer as WalkableLayer).name = name;
                    break;
            }
        }


        public function addTileset(tileset:Tileset):void {
            _maxTilesetID++;
            tileset.id = _maxTilesetID;
            tilesetList.push(tileset);
        }

        public function getWalkableValue(tx:int, ty:int):int {
            if(tx < 0 || ty < 0)
                return -1;
            var tileInfo:TileInfo = _walkableLayer.getInfo(tx, ty);
            if (tileInfo != null)
                return tileInfo.tileX;
            else
                return -1;
        }

        public function getUpperLayerCutRect(tx:int, ty:int):Vector.<IntPoint> {
            return _upperLayers[1].getConnexRect(tx, ty);
        }
    }
}